rm(list=ls(all=TRUE))
library(tidyverse)
library(magrittr)
library(jsonlite)
library(broom)
Read example json file:
results_dir <- "microbeMASST_results/"
json_example <- read_json(str_c(results_dir, "fastMASST_HILIC_neg__165_microbe.json"), simplifyVector = F)
Define function for iterating over nodes in the MASST output:
iterate_masst <- function(masst_node){
node_attributes <- names(masst_node)
if ("Rank" %in% node_attributes && masst_node$Rank == "species") {
tibble(
NAME = masst_node$name,
TYPE = masst_node$type,
NCBI = masst_node$NCBI,
RANK = masst_node$Rank,
GROUP_SIZE = masst_node$group_size,
MATCHED_SIZE = masst_node$matched_size
)
}
else {
if ("type" %in% node_attributes && masst_node$type == "node") {
lapply(masst_node$children, iterate_masst) %>%
bind_rows()
}
else {
tibble(
NAME = character(),
TYPE = character(),
NCBI = character(),
RANK = character(),
GROUP_SIZE = integer(),
MATCHED_SIZE = integer()
)
}
}
}
iterate_masst(json_example)
Set up tibble initialized with all json file names:
masst_results <- tibble(FILE_NAME = dir(results_dir, ".*\\.json"))
masst_results
Parse info from file names:
masst_results <- masst_results %>%
mutate(
SEARCH_TYPE = FILE_NAME %>% str_extract("food|microbe"),
DATASET = FILE_NAME %>% str_extract("(HILIC|RP).+(pos|neg)"),
DATASET = if_else(DATASET == "RP_neg", "RP18_neg", DATASET),
SCAN = FILE_NAME %>% str_extract("__.+_") %>% str_extract("[0-9]+"),
FEATURE_ID = str_c(
case_when(
DATASET == "RP18_pos" ~ "X94",
DATASET == "RP18_neg" ~ "X95",
DATASET == "HILIC_pos" ~ "X96",
DATASET == "HILIC_neg" ~ "X97"
),
SCAN %>% str_pad(max(nchar(SCAN)), "left", "0")
)
)
masst_results
Number of results files per dataset:
masst_results %>%
count(DATASET)
Read json files for microbeMASST:
masst_results$JSON[[1]][setdiff(names(masst_results$JSON[[1]]), c("children", "pie_data"))]
$name
[1] "root"
$duplication
[1] "Y"
$type
[1] "node"
$NCBI
[1] "131567"
$Rank
[1] "cellular organisms"
$group_size
[1] 72560
$matched_size
[1] 3
$occurrence_fraction
[1] 4.134509e-05
Add stats for species:
masst_results <- masst_results %>%
mutate(
STATS_ROOT = JSON %>% map(~ tibble(ROOT_GROUP_SIZE = .$group_size, ROOT_MATCHED_SIZE = .$matched_size)),
STATS_PHYLUM = JSON %>% map(iterate_masst)
)
masst_results$STATS_ROOT[[1]]
masst_results$STATS_PHYLUM[[1]]
Select relevant columns and unnest stats:
masst_results <- masst_results %>%
select(FEATURE_ID, DATASET, SEARCH_TYPE, STATS_ROOT, STATS_PHYLUM) %>%
unnest(c(STATS_ROOT, STATS_PHYLUM))
masst_results
Check: Is there any other TYPE than “node”?
masst_results$TYPE %>% unique()
[1] "leaf" "node"
Check: Is there any other RANK than “species”?
masst_results$RANK %>% unique()
[1] "species"
Perform Fisher’s exact test for the association between features and
species:
masst_results <- masst_results %>%
filter(MATCHED_SIZE > 0) %>%
mutate(
FISHER = pmap(
list(
ROOT_GROUP_SIZE,
ROOT_MATCHED_SIZE,
GROUP_SIZE,
MATCHED_SIZE
),
~ fisher.test(
matrix(
c(..1, ..2, ..3, ..4),
nrow = 2
)
)
),
FISHER = FISHER %>% map(tidy)
) %>%
unnest(FISHER)
masst_results
Perform correction for multiple testing and check distribution of
p-values:
masst_results <- masst_results %>%
mutate(p.value.fdr = p.value %>% p.adjust(method = "fdr"))
masst_results %>%
ggplot() +
geom_point(aes(p.value, p.value.fdr)) +
geom_abline(slope = 1)

masst_results %>%
ggplot() +
geom_histogram(aes(p.value.fdr, fill = DATASET), bins = 100) +
scale_x_continuous(breaks = 0:5/5)

masst_results %>%
filter(p.value.fdr < 0.1) %>%
ggplot() +
geom_histogram(aes(p.value.fdr, fill = DATASET), bins = 100) +
scale_x_continuous(breaks = 0:5/50)

masst_results %>%
filter(p.value.fdr < 0.01) %>%
ggplot() +
geom_histogram(aes(p.value.fdr, fill = DATASET), bins = 100) +
scale_x_continuous(breaks = 0:5/500)

masst_results %>%
filter(p.value.fdr < 0.001) %>%
ggplot() +
geom_histogram(aes(p.value.fdr, fill = DATASET), bins = 100) +
scale_x_continuous(breaks = 0:5/5000)

Filter for a p-value < 0.01:
masst_results <- masst_results %>%
filter(p.value.fdr < 0.01)
masst_results
Number of significant hits per dataset:
masst_results %>%
count(DATASET)
Number of features with significant hits per dataset:
masst_results %>%
group_by(DATASET) %>%
summarize(N_FEATURES = n_distinct(FEATURE_ID))
Map MASST results from features to families
Read feature annotations from file:
feature_info <- rbind(
read_tsv("feature_metadata/C18neg_feature_metadata_consolidated_is_microbial.tsv", guess_max = 100000) %>%
mutate(MET_CHEM_NO = paste0("X95", formatC(`#featureID`, width = 5, flag = "0", format = "d"))) %>%
mutate(FAMILY_ID = paste0("X95", formatC(GNPS_componentindex, width = 4, flag = "0", format = "d"))),
read_tsv("feature_metadata/C18pos_feature_metadata_consolidated_is_microbial.tsv", guess_max = 100000) %>%
mutate(MET_CHEM_NO = paste0("X94", formatC(`#featureID`, width = 5, flag = "0", format = "d"))) %>%
mutate(FAMILY_ID = paste0("X94", formatC(GNPS_componentindex, width = 4, flag = "0", format = "d"))),
read_tsv("feature_metadata/HILICneg_feature_metadata_consolidated_is_microbial.tsv", guess_max = 100000) %>%
mutate(MET_CHEM_NO = paste0("X97", formatC(`#featureID`, width = 5, flag = "0", format = "d"))) %>%
mutate(FAMILY_ID = paste0("X97", formatC(GNPS_componentindex, width = 4, flag = "0", format = "d"))),
read_tsv("feature_metadata/HILICpos_feature_metadata_consolidated_is_microbial.tsv", guess_max = 100000) %>%
mutate(MET_CHEM_NO = paste0("X96", formatC(`#featureID`, width = 5, flag = "0", format = "d"))) %>%
mutate(FAMILY_ID = paste0("X96", formatC(GNPS_componentindex, width = 4, flag = "0", format = "d")))
) %>%
mutate(FAMILY_ID = if_else(str_detect(FAMILY_ID, "-001$"), "Singleton", FAMILY_ID))
Rows: 6155 Columns: 256
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr (159): GNPS_Best Ion, GNPS_GNPSLinkout_Cluster, GNPS_GNPSLinkout_Network, GNPS_INCHI, GNPS_LibraryID, GNPS_MS2 Verification Comment, GNPS_S...
dbl (91): #featureID, GNPS_Annotated Adduct Features ID, GNPS_Correlated Features Group ID, GNPS_G1, GNPS_G2, GNPS_G3, GNPS_G4, GNPS_G5, GNPS_...
lgl (6): GNPS_LIB_Pubmed_ID, GNPS_LIB_INCHI_AUX, GNPS_LIB_tags, GNPS_LIBA_INCHI_AUX, GNPS_LIBA_tags, CSI_ConfidenceScore
i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
Rows: 15131 Columns: 256
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr (158): GNPS_GNPSLinkout_Cluster, GNPS_GNPSLinkout_Network, GNPS_INCHI, GNPS_LibraryID, GNPS_Smiles, GNPS_SpectrumID, GNPS_LIB_SpectrumID, G...
dbl (90): #featureID, GNPS_G1, GNPS_G2, GNPS_G3, GNPS_G4, GNPS_G5, GNPS_G6, GNPS_MQScore, GNPS_RTConsensus, GNPS_RTMean, GNPS_RTStdErr, GNPS_S...
lgl (8): GNPS_Annotated Adduct Features ID, GNPS_Best Ion, GNPS_Correlated Features Group ID, GNPS_MS2 Verification Comment, GNPS_neutral M m...
i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
Rows: 11230 Columns: 256
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr (153): GNPS_GNPSLinkout_Cluster, GNPS_GNPSLinkout_Network, GNPS_INCHI, GNPS_LibraryID, GNPS_Smiles, GNPS_SpectrumID, GNPS_LIB_SpectrumID, G...
dbl (89): #featureID, GNPS_Correlated Features Group ID, GNPS_G1, GNPS_G2, GNPS_G3, GNPS_G4, GNPS_G5, GNPS_G6, GNPS_MQScore, GNPS_RTConsensus,...
lgl (14): GNPS_Annotated Adduct Features ID, GNPS_Best Ion, GNPS_MS2 Verification Comment, GNPS_neutral M mass, GNPS_LIB_INCHI_AUX, GNPS_LIB_t...
i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
Rows: 28762 Columns: 256
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr (161): GNPS_Best Ion, GNPS_GNPSLinkout_Cluster, GNPS_GNPSLinkout_Network, GNPS_INCHI, GNPS_LibraryID, GNPS_MS2 Verification Comment, GNPS_S...
dbl (92): #featureID, GNPS_Annotated Adduct Features ID, GNPS_Correlated Features Group ID, GNPS_G1, GNPS_G2, GNPS_G3, GNPS_G4, GNPS_G5, GNPS_...
lgl (3): GNPS_LIB_INCHI_AUX, GNPS_LIBA_INCHI_AUX, CSI_ConfidenceScore
i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
Add FAMILY_ID to the MASST results:
masst_results <- masst_results %>%
inner_join(
feature_info %>%
select(FEATURE_ID = MET_CHEM_NO, FAMILY_ID)
)
Joining, by = "FEATURE_ID"
masst_results
Number of families with significant hits per dataset:
masst_results %>%
group_by(DATASET) %>%
summarize(N_FAMILIES = n_distinct(FAMILY_ID), n = n())
Statistical analysis of features
Read statistical results from file:
skin_p_cat_dir <- read_tsv("Untargeted.p_cat_dir.tsv")
Rows: 33333 Columns: 15
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr (15): MET_CHEM_NO, p_cat_dir|base|sebum, p_cat_dir|base|skicon, p_cat_dir|groups|oily-norm, p_cat_dir|groups|skicon, p_cat_dir|oily|exfol, ...
i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
skin_p_value <- read_tsv("Untargeted.p_value.tsv")
Rows: 44567 Columns: 15
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr (1): MET_CHEM_NO
dbl (14): p_value|base|sebum, p_value|base|skicon, p_value|groups|oily-norm, p_value|groups|skicon, p_value|oily|exfol, p_value|oily|F-B, p_val...
i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
skin_p_cat_dir %>% colnames()
[1] "MET_CHEM_NO" "p_cat_dir|base|sebum" "p_cat_dir|base|skicon" "p_cat_dir|groups|oily-norm"
[5] "p_cat_dir|groups|skicon" "p_cat_dir|oily|exfol" "p_cat_dir|oily|F-B" "p_cat_dir|oily|F-B|A"
[9] "p_cat_dir|oily|F-B|B" "p_cat_dir|oily|F-B|B-A" "p_cat_dir|oily|F-B|C" "p_cat_dir|oily|F-B|C-A"
[13] "p_cat_dir|oily|F-B|C-B" "p_cat_dir|oily|sebum" "p_cat_dir|oily|skicon"
skin_stats <- skin_p_value %>%
select(MET_CHEM_NO) %>%
left_join(skin_p_cat_dir, by = "MET_CHEM_NO") %>%
mutate(
sebumeter_0.1_any = `p_cat_dir|base|sebum` %>% is.na(.) %>% not(),
sebumeter_0.1_up = `p_cat_dir|base|sebum` %>% is.na(.) %>% not() & `p_cat_dir|base|sebum` %>% str_detect("Up"),
sebumeter_0.1_down = `p_cat_dir|base|sebum` %>% is.na(.) %>% not() & `p_cat_dir|base|sebum` %>% str_detect("Dn")
)
skin_stats %>%
group_by(`p_cat_dir|base|sebum`, sebumeter_0.1_any) %>% summarize(.groups = "drop")
skin_stats %>%
group_by(`p_cat_dir|base|sebum`, sebumeter_0.1_up) %>% summarize(.groups = "drop")
skin_stats %>%
group_by(`p_cat_dir|base|sebum`, sebumeter_0.1_down) %>% summarize(.groups = "drop")
Check whether there are skin stats for all features from the MASST
results:
masst_results_stats <- masst_results %>%
inner_join(
skin_stats %>%
select(FEATURE_ID = MET_CHEM_NO, sebumeter_0.1_any, sebumeter_0.1_up, sebumeter_0.1_down),
by = "FEATURE_ID"
)
setdiff(masst_results$FEATURE_ID, skin_stats$MET_CHEM_NO)
character(0)
Bacteria in Fig. 4 (MMvec)
Staphylococcus epidermidis
Are there any masst hits for Staphylococcus epidermidis?
masst_results %>%
filter(NAME %>% str_to_lower() %>% str_detect("staph")) %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
Which of these are correlated with sebumeter score?
masst_results_stats %>%
filter(NAME %>% str_to_lower() %>% str_detect("staph") & sebumeter_0.1_any) %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
Which of these are in one of the families correlated with sebumeter
score?
masst_results_stats %>%
filter(
NAME %>% str_to_lower() %>% str_detect("staph") &
FAMILY_ID %in% c("X940029", "X950190", "X940005", "X950167", "X950477", "X970034")
) %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
Propionibacterium acnes
Are there any masst hits for Propionibacterium acnes?
masst_results %>%
filter(NAME %>% str_to_lower() %>% str_detect("propionibac")) %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
Which of these are correlated with sebumeter score?
masst_results_stats %>%
filter(NAME %>% str_to_lower() %>% str_detect("propionibac") & sebumeter_0.1_any) %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
Which of these are in one of the families correlated with sebumeter
score?
masst_results_stats %>%
filter(
NAME %>% str_to_lower() %>% str_detect("propionibac") &
FAMILY_ID %in% c("X940029", "X950190", "X940005", "X950167", "X950477", "X970034")
) %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
Species in the families correlated with sebumeter score
Family X940029
Which species are in the family X940029?
masst_results %>%
filter(FAMILY_ID == "X940029") %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
masst_results %>%
filter(FAMILY_ID == "X940029") %>%
count(NAME)
Which features with MASST hits are in the family X940029?
masst_results %>%
filter(FAMILY_ID == "X940029") %>%
select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>%
arrange(FAMILY_ID, FEATURE_ID, NAME)
masst_results %>%
filter(FAMILY_ID == "X940029") %>%
count(FAMILY_ID, FEATURE_ID)
Family X950190
Which species are in the family X950190?
masst_results %>%
filter(FAMILY_ID == "X950190") %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
Which features with MASST hits are in the family X950190?
masst_results %>%
filter(FAMILY_ID == "X950190") %>%
select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>%
arrange(FAMILY_ID, FEATURE_ID, NAME)
Family X940005
Which species are in the family X940005?
masst_results %>%
filter(FAMILY_ID == "X940005") %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
masst_results %>%
filter(FAMILY_ID == "X940005") %>%
count(NAME)
Which features with MASST hits are in the family X940005?
masst_results %>%
filter(FAMILY_ID == "X940005") %>%
select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>%
arrange(FAMILY_ID, FEATURE_ID, NAME)
masst_results %>%
filter(FAMILY_ID == "X940005") %>%
count(FAMILY_ID, FEATURE_ID)
Family X950167
Which species are in the family X950167?
masst_results %>%
filter(FAMILY_ID == "X950167") %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
masst_results %>%
filter(FAMILY_ID == "X950167") %>%
count(NAME)
Which features with MASST hits are in the family X950167?
masst_results %>%
filter(FAMILY_ID == "X950167") %>%
select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>%
arrange(FAMILY_ID, FEATURE_ID, NAME)
masst_results %>%
filter(FAMILY_ID == "X950167") %>%
count(FAMILY_ID, FEATURE_ID)
Family X950477
Which species are in the family X950477?
masst_results %>%
filter(FAMILY_ID == "X950477") %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
masst_results %>%
filter(FAMILY_ID == "X950477") %>%
count(NAME)
Which features with MASST hits are in the family X950477?
masst_results %>%
filter(FAMILY_ID == "X950477") %>%
select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>%
arrange(FAMILY_ID, FEATURE_ID, NAME)
masst_results %>%
filter(FAMILY_ID == "X950477") %>%
count(FAMILY_ID, FEATURE_ID)
Family X970034
Which species are in the family X970034?
masst_results %>%
filter(FAMILY_ID == "X970034") %>%
select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>%
arrange(NAME, FAMILY_ID, FEATURE_ID)
masst_results %>%
filter(FAMILY_ID == "X970034") %>%
count(NAME)
Which features with MASST hits are in the family X970034?
masst_results %>%
filter(FAMILY_ID == "X970034") %>%
select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>%
arrange(FAMILY_ID, FEATURE_ID, NAME)
masst_results %>%
filter(FAMILY_ID == "X970034") %>%
count(FAMILY_ID, FEATURE_ID)
Session info
sessionInfo()
R version 4.1.2 (2021-11-01)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19044)
Matrix products: default
locale:
[1] LC_COLLATE=English_United States.1252 LC_CTYPE=English_United States.1252 LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C LC_TIME=English_United States.1252
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] broom_0.7.11 jsonlite_1.7.2 magrittr_2.0.1 forcats_0.5.1 stringr_1.4.0 dplyr_1.0.7 purrr_0.3.4 readr_2.1.1
[9] tidyr_1.1.4 tibble_3.1.6 ggplot2_3.3.5 tidyverse_1.3.2
loaded via a namespace (and not attached):
[1] Rcpp_1.0.8 lubridate_1.8.0 assertthat_0.2.1 digest_0.6.29 utf8_1.2.2 R6_2.5.1 cellranger_1.1.0
[8] backports_1.4.1 reprex_2.0.1 evaluate_0.19 httr_1.4.2 pillar_1.6.4 rlang_0.4.12 googlesheets4_1.0.0
[15] readxl_1.3.1 rstudioapi_0.13 jquerylib_0.1.4 rmarkdown_2.11 labeling_0.4.2 googledrive_2.0.0 bit_4.0.4
[22] munsell_0.5.0 tinytex_0.36 compiler_4.1.2 modelr_0.1.8 xfun_0.35 pkgconfig_2.0.3 htmltools_0.5.2
[29] tidyselect_1.1.1 fansi_0.5.0 crayon_1.4.2 tzdb_0.2.0 dbplyr_2.1.1 withr_2.4.3 grid_4.1.2
[36] gtable_0.3.0 lifecycle_1.0.1 DBI_1.1.2 scales_1.1.1 cli_3.1.0 stringi_1.7.6 vroom_1.5.7
[43] farver_2.1.0 fs_1.5.2 xml2_1.3.3 ellipsis_0.3.2 generics_0.1.1 vctrs_0.3.8 tools_4.1.2
[50] bit64_4.0.5 glue_1.6.0 hms_1.1.1 parallel_4.1.2 fastmap_1.1.0 yaml_2.2.1 colorspace_2.0-2
[57] gargle_1.2.0 rvest_1.0.2 knitr_1.41 haven_2.4.3
LS0tDQp0aXRsZTogIm1pY3JvYmVNQVNTVCBTcGVjaWVzIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3J9DQpybShsaXN0PWxzKGFsbD1UUlVFKSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkoanNvbmxpdGUpDQpsaWJyYXJ5KGJyb29tKQ0KYGBgDQoNClJlYWQgZXhhbXBsZSBqc29uIGZpbGU6DQpgYGB7cn0NCnJlc3VsdHNfZGlyIDwtICJtaWNyb2JlTUFTU1RfcmVzdWx0cy8iDQpqc29uX2V4YW1wbGUgPC0gcmVhZF9qc29uKHN0cl9jKHJlc3VsdHNfZGlyLCAiZmFzdE1BU1NUX0hJTElDX25lZ19fMTY1X21pY3JvYmUuanNvbiIpLCBzaW1wbGlmeVZlY3RvciA9IEYpDQpgYGANCg0KRGVmaW5lIGZ1bmN0aW9uIGZvciBpdGVyYXRpbmcgb3ZlciBub2RlcyBpbiB0aGUgTUFTU1Qgb3V0cHV0Og0KYGBge3J9DQppdGVyYXRlX21hc3N0IDwtIGZ1bmN0aW9uKG1hc3N0X25vZGUpew0KICBub2RlX2F0dHJpYnV0ZXMgPC0gbmFtZXMobWFzc3Rfbm9kZSkNCiAgaWYgKCJSYW5rIiAlaW4lIG5vZGVfYXR0cmlidXRlcyAmJiBtYXNzdF9ub2RlJFJhbmsgPT0gInNwZWNpZXMiKSB7DQogICAgdGliYmxlKA0KICAgICAgTkFNRSA9IG1hc3N0X25vZGUkbmFtZSwgDQogICAgICBUWVBFID0gbWFzc3Rfbm9kZSR0eXBlLCANCiAgICAgIE5DQkkgPSBtYXNzdF9ub2RlJE5DQkksIA0KICAgICAgUkFOSyA9IG1hc3N0X25vZGUkUmFuaywgDQogICAgICBHUk9VUF9TSVpFID0gbWFzc3Rfbm9kZSRncm91cF9zaXplLCANCiAgICAgIE1BVENIRURfU0laRSA9IG1hc3N0X25vZGUkbWF0Y2hlZF9zaXplDQogICAgICApDQogIH0NCiAgZWxzZSB7DQogICAgaWYgKCJ0eXBlIiAlaW4lIG5vZGVfYXR0cmlidXRlcyAmJiBtYXNzdF9ub2RlJHR5cGUgPT0gIm5vZGUiKSB7DQogICAgICBsYXBwbHkobWFzc3Rfbm9kZSRjaGlsZHJlbiwgaXRlcmF0ZV9tYXNzdCkgJT4lIA0KICAgICAgICBiaW5kX3Jvd3MoKQ0KICAgIH0NCiAgICBlbHNlIHsNCiAgICAgIHRpYmJsZSgNCiAgICAgICAgTkFNRSA9IGNoYXJhY3RlcigpLCANCiAgICAgICAgVFlQRSA9IGNoYXJhY3RlcigpLCANCiAgICAgICAgTkNCSSA9IGNoYXJhY3RlcigpLCANCiAgICAgICAgUkFOSyA9IGNoYXJhY3RlcigpLCANCiAgICAgICAgR1JPVVBfU0laRSA9IGludGVnZXIoKSwgDQogICAgICAgIE1BVENIRURfU0laRSA9IGludGVnZXIoKQ0KICAgICAgICApDQogICAgfQ0KICB9DQp9DQoNCml0ZXJhdGVfbWFzc3QoanNvbl9leGFtcGxlKQ0KYGBgDQoNClNldCB1cCB0aWJibGUgaW5pdGlhbGl6ZWQgd2l0aCBhbGwganNvbiBmaWxlIG5hbWVzOg0KYGBge3J9DQptYXNzdF9yZXN1bHRzIDwtIHRpYmJsZShGSUxFX05BTUUgPSBkaXIocmVzdWx0c19kaXIsICIuKlxcLmpzb24iKSkNCg0KbWFzc3RfcmVzdWx0cw0KYGBgDQoNClBhcnNlIGluZm8gZnJvbSBmaWxlIG5hbWVzOg0KYGBge3J9DQptYXNzdF9yZXN1bHRzIDwtIG1hc3N0X3Jlc3VsdHMgJT4lIA0KICBtdXRhdGUoDQogICAgU0VBUkNIX1RZUEUgPSBGSUxFX05BTUUgJT4lIHN0cl9leHRyYWN0KCJmb29kfG1pY3JvYmUiKSwNCiAgICBEQVRBU0VUID0gRklMRV9OQU1FICU+JSBzdHJfZXh0cmFjdCgiKEhJTElDfFJQKS4rKHBvc3xuZWcpIiksDQogICAgREFUQVNFVCA9IGlmX2Vsc2UoREFUQVNFVCA9PSAiUlBfbmVnIiwgIlJQMThfbmVnIiwgREFUQVNFVCksDQogICAgU0NBTiA9IEZJTEVfTkFNRSAlPiUgc3RyX2V4dHJhY3QoIl9fLitfIikgJT4lIHN0cl9leHRyYWN0KCJbMC05XSsiKSwNCiAgICBGRUFUVVJFX0lEID0gc3RyX2MoDQogICAgICBjYXNlX3doZW4oDQogICAgICAgIERBVEFTRVQgPT0gIlJQMThfcG9zIiAgfiAiWDk0IiwNCiAgICAgICAgREFUQVNFVCA9PSAiUlAxOF9uZWciICB+ICJYOTUiLA0KICAgICAgICBEQVRBU0VUID09ICJISUxJQ19wb3MiIH4gIlg5NiIsDQogICAgICAgIERBVEFTRVQgPT0gIkhJTElDX25lZyIgfiAiWDk3Ig0KICAgICAgKSwNCiAgICAgIFNDQU4gJT4lIHN0cl9wYWQobWF4KG5jaGFyKFNDQU4pKSwgImxlZnQiLCAiMCIpDQogICAgKQ0KICApDQoNCm1hc3N0X3Jlc3VsdHMNCmBgYA0KDQpOdW1iZXIgb2YgcmVzdWx0cyBmaWxlcyBwZXIgZGF0YXNldDoNCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGNvdW50KERBVEFTRVQpDQpgYGANCg0KUmVhZCBqc29uIGZpbGVzIGZvciBtaWNyb2JlTUFTU1Q6DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgPC0gbWFzc3RfcmVzdWx0cyAlPiUgDQogIG11dGF0ZSgNCiAgICBQQVRIID0gc3RyX2MocmVzdWx0c19kaXIsIEZJTEVfTkFNRSksDQogICAgSlNPTiA9IFBBVEggJT4lIG1hcChyZWFkX2pzb24pLA0KICAgIFNUQVRTX1BIWUxVTSA9IEpTT04gJT4lIG1hcChpdGVyYXRlX21hc3N0KQ0KICApDQoNCm1hc3N0X3Jlc3VsdHMkSlNPTltbMV1dW3NldGRpZmYobmFtZXMobWFzc3RfcmVzdWx0cyRKU09OW1sxXV0pLCBjKCJjaGlsZHJlbiIsICJwaWVfZGF0YSIpKV0NCmBgYA0KDQpBZGQgc3RhdHMgZm9yIHNwZWNpZXM6DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgPC0gbWFzc3RfcmVzdWx0cyAlPiUgDQogIG11dGF0ZSgNCiAgICBTVEFUU19ST09UICAgPSBKU09OICU+JSBtYXAofiB0aWJibGUoUk9PVF9HUk9VUF9TSVpFID0gLiRncm91cF9zaXplLCBST09UX01BVENIRURfU0laRSA9IC4kbWF0Y2hlZF9zaXplKSksDQogICAgU1RBVFNfUEhZTFVNID0gSlNPTiAlPiUgbWFwKGl0ZXJhdGVfbWFzc3QpDQogICkNCg0KbWFzc3RfcmVzdWx0cyRTVEFUU19ST09UW1sxXV0NCm1hc3N0X3Jlc3VsdHMkU1RBVFNfUEhZTFVNW1sxXV0NCmBgYA0KDQpTZWxlY3QgcmVsZXZhbnQgY29sdW1ucyBhbmQgdW5uZXN0IHN0YXRzOg0KYGBge3J9DQptYXNzdF9yZXN1bHRzIDwtIG1hc3N0X3Jlc3VsdHMgJT4lIA0KICBzZWxlY3QoRkVBVFVSRV9JRCwgREFUQVNFVCwgU0VBUkNIX1RZUEUsIFNUQVRTX1JPT1QsIFNUQVRTX1BIWUxVTSkgJT4lIA0KICB1bm5lc3QoYyhTVEFUU19ST09ULCBTVEFUU19QSFlMVU0pKQ0KDQptYXNzdF9yZXN1bHRzDQpgYGANCg0KQ2hlY2s6IElzIHRoZXJlIGFueSBvdGhlciBgVFlQRWAgdGhhbiAibm9kZSI/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMkVFlQRSAlPiUgdW5pcXVlKCkNCmBgYA0KDQpDaGVjazogSXMgdGhlcmUgYW55IG90aGVyIGBSQU5LYCB0aGFuICJzcGVjaWVzIj8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyRSQU5LICU+JSB1bmlxdWUoKQ0KYGBgDQoNClBlcmZvcm0gRmlzaGVyJ3MgZXhhY3QgdGVzdCBmb3IgdGhlIGFzc29jaWF0aW9uIGJldHdlZW4gZmVhdHVyZXMgYW5kIHNwZWNpZXM6DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgPC0gbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihNQVRDSEVEX1NJWkUgPiAwKSAlPiUgDQogIG11dGF0ZSgNCiAgICBGSVNIRVIgPSBwbWFwKA0KICAgICAgbGlzdCgNCiAgICAgICAgUk9PVF9HUk9VUF9TSVpFLCANCiAgICAgICAgUk9PVF9NQVRDSEVEX1NJWkUsIA0KICAgICAgICBHUk9VUF9TSVpFLCANCiAgICAgICAgTUFUQ0hFRF9TSVpFDQogICAgICApLA0KICAgICAgfiBmaXNoZXIudGVzdCgNCiAgICAgICAgbWF0cml4KA0KICAgICAgICAgIGMoLi4xLCAuLjIsIC4uMywgLi40KSwNCiAgICAgICAgICBucm93ID0gMg0KICAgICAgICApDQogICAgICApDQogICAgKSwNCiAgICBGSVNIRVIgPSBGSVNIRVIgJT4lIG1hcCh0aWR5KQ0KICApICU+JSANCiAgdW5uZXN0KEZJU0hFUikNCg0KbWFzc3RfcmVzdWx0cw0KYGBgDQoNClBlcmZvcm0gY29ycmVjdGlvbiBmb3IgbXVsdGlwbGUgdGVzdGluZyBhbmQgY2hlY2sgZGlzdHJpYnV0aW9uIG9mIHAtdmFsdWVzOg0KYGBge3J9DQptYXNzdF9yZXN1bHRzIDwtIG1hc3N0X3Jlc3VsdHMgJT4lIA0KICBtdXRhdGUocC52YWx1ZS5mZHIgPSBwLnZhbHVlICU+JSBwLmFkanVzdChtZXRob2QgPSAiZmRyIikpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBnZ3Bsb3QoKSArIA0KICBnZW9tX3BvaW50KGFlcyhwLnZhbHVlLCBwLnZhbHVlLmZkcikpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxKQ0KDQptYXNzdF9yZXN1bHRzICU+JSANCiAgZ2dwbG90KCkgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMocC52YWx1ZS5mZHIsIGZpbGwgPSBEQVRBU0VUKSwgYmlucyA9IDEwMCkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMDo1LzUpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIocC52YWx1ZS5mZHIgPCAwLjEpICU+JSANCiAgZ2dwbG90KCkgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMocC52YWx1ZS5mZHIsIGZpbGwgPSBEQVRBU0VUKSwgYmlucyA9IDEwMCkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMDo1LzUwKQ0KDQptYXNzdF9yZXN1bHRzICU+JSANCiAgICBmaWx0ZXIocC52YWx1ZS5mZHIgPCAwLjAxKSAlPiUgDQogIGdncGxvdCgpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHAudmFsdWUuZmRyLCBmaWxsID0gREFUQVNFVCksIGJpbnMgPSAxMDApICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IDA6NS81MDApDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIocC52YWx1ZS5mZHIgPCAwLjAwMSkgJT4lIA0KICBnZ3Bsb3QoKSArDQogIGdlb21faGlzdG9ncmFtKGFlcyhwLnZhbHVlLmZkciwgZmlsbCA9IERBVEFTRVQpLCBiaW5zID0gMTAwKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAwOjUvNTAwMCkNCmBgYA0KDQpGaWx0ZXIgZm9yIGEgcC12YWx1ZSA8IDAuMDE6DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgPC0gbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihwLnZhbHVlLmZkciA8IDAuMDEpDQoNCm1hc3N0X3Jlc3VsdHMNCmBgYA0KDQpOdW1iZXIgb2Ygc2lnbmlmaWNhbnQgaGl0cyBwZXIgZGF0YXNldDoNCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGNvdW50KERBVEFTRVQpDQpgYGANCg0KTnVtYmVyIG9mIGZlYXR1cmVzIHdpdGggc2lnbmlmaWNhbnQgaGl0cyBwZXIgZGF0YXNldDoNCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGdyb3VwX2J5KERBVEFTRVQpICU+JSANCiAgc3VtbWFyaXplKE5fRkVBVFVSRVMgPSBuX2Rpc3RpbmN0KEZFQVRVUkVfSUQpKQ0KYGBgDQoNCiMgTWFwIE1BU1NUIHJlc3VsdHMgZnJvbSBmZWF0dXJlcyB0byBmYW1pbGllcw0KDQpSZWFkIGZlYXR1cmUgYW5ub3RhdGlvbnMgZnJvbSBmaWxlOg0KYGBge3J9DQpmZWF0dXJlX2luZm8gPC0gcmJpbmQoDQogIHJlYWRfdHN2KCJmZWF0dXJlX21ldGFkYXRhL0MxOG5lZ19mZWF0dXJlX21ldGFkYXRhX2NvbnNvbGlkYXRlZF9pc19taWNyb2JpYWwudHN2IiwgZ3Vlc3NfbWF4ID0gMTAwMDAwKSAlPiUgDQogICAgbXV0YXRlKE1FVF9DSEVNX05PID0gcGFzdGUwKCJYOTUiLCBmb3JtYXRDKGAjZmVhdHVyZUlEYCwgICAgICAgIHdpZHRoID0gNSwgZmxhZyA9ICIwIiwgZm9ybWF0ID0gImQiKSkpICU+JSANCiAgICBtdXRhdGUoRkFNSUxZX0lEICAgPSBwYXN0ZTAoIlg5NSIsIGZvcm1hdEMoR05QU19jb21wb25lbnRpbmRleCwgd2lkdGggPSA0LCBmbGFnID0gIjAiLCBmb3JtYXQgPSAiZCIpKSksDQogIHJlYWRfdHN2KCJmZWF0dXJlX21ldGFkYXRhL0MxOHBvc19mZWF0dXJlX21ldGFkYXRhX2NvbnNvbGlkYXRlZF9pc19taWNyb2JpYWwudHN2IiwgZ3Vlc3NfbWF4ID0gMTAwMDAwKSAlPiUgDQogICAgbXV0YXRlKE1FVF9DSEVNX05PID0gcGFzdGUwKCJYOTQiLCBmb3JtYXRDKGAjZmVhdHVyZUlEYCwgICAgICAgIHdpZHRoID0gNSwgZmxhZyA9ICIwIiwgZm9ybWF0ID0gImQiKSkpICU+JSANCiAgICBtdXRhdGUoRkFNSUxZX0lEICAgPSBwYXN0ZTAoIlg5NCIsIGZvcm1hdEMoR05QU19jb21wb25lbnRpbmRleCwgd2lkdGggPSA0LCBmbGFnID0gIjAiLCBmb3JtYXQgPSAiZCIpKSksDQogIHJlYWRfdHN2KCJmZWF0dXJlX21ldGFkYXRhL0hJTElDbmVnX2ZlYXR1cmVfbWV0YWRhdGFfY29uc29saWRhdGVkX2lzX21pY3JvYmlhbC50c3YiLCBndWVzc19tYXggPSAxMDAwMDApICU+JSANCiAgICBtdXRhdGUoTUVUX0NIRU1fTk8gPSBwYXN0ZTAoIlg5NyIsIGZvcm1hdEMoYCNmZWF0dXJlSURgLCAgICAgICAgd2lkdGggPSA1LCBmbGFnID0gIjAiLCBmb3JtYXQgPSAiZCIpKSkgJT4lIA0KICAgIG11dGF0ZShGQU1JTFlfSUQgICA9IHBhc3RlMCgiWDk3IiwgZm9ybWF0QyhHTlBTX2NvbXBvbmVudGluZGV4LCB3aWR0aCA9IDQsIGZsYWcgPSAiMCIsIGZvcm1hdCA9ICJkIikpKSwNCiAgcmVhZF90c3YoImZlYXR1cmVfbWV0YWRhdGEvSElMSUNwb3NfZmVhdHVyZV9tZXRhZGF0YV9jb25zb2xpZGF0ZWRfaXNfbWljcm9iaWFsLnRzdiIsIGd1ZXNzX21heCA9IDEwMDAwMCkgJT4lIA0KICAgIG11dGF0ZShNRVRfQ0hFTV9OTyA9IHBhc3RlMCgiWDk2IiwgZm9ybWF0QyhgI2ZlYXR1cmVJRGAsICAgICAgICB3aWR0aCA9IDUsIGZsYWcgPSAiMCIsIGZvcm1hdCA9ICJkIikpKSAlPiUgDQogICAgbXV0YXRlKEZBTUlMWV9JRCAgID0gcGFzdGUwKCJYOTYiLCBmb3JtYXRDKEdOUFNfY29tcG9uZW50aW5kZXgsIHdpZHRoID0gNCwgZmxhZyA9ICIwIiwgZm9ybWF0ID0gImQiKSkpDQogICkgJT4lIA0KICBtdXRhdGUoRkFNSUxZX0lEID0gaWZfZWxzZShzdHJfZGV0ZWN0KEZBTUlMWV9JRCwgIi0wMDEkIiksICJTaW5nbGV0b24iLCBGQU1JTFlfSUQpKQ0KYGBgDQoNCkFkZCBGQU1JTFlfSUQgdG8gdGhlIE1BU1NUIHJlc3VsdHM6DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgPC0gbWFzc3RfcmVzdWx0cyAlPiUgDQogIGlubmVyX2pvaW4oDQogICAgZmVhdHVyZV9pbmZvICU+JSANCiAgICAgIHNlbGVjdChGRUFUVVJFX0lEID0gTUVUX0NIRU1fTk8sIEZBTUlMWV9JRCkNCiAgKQ0KDQptYXNzdF9yZXN1bHRzDQpgYGANCg0KTnVtYmVyIG9mIGZhbWlsaWVzIHdpdGggc2lnbmlmaWNhbnQgaGl0cyBwZXIgZGF0YXNldDoNCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGdyb3VwX2J5KERBVEFTRVQpICU+JSANCiAgc3VtbWFyaXplKE5fRkFNSUxJRVMgPSBuX2Rpc3RpbmN0KEZBTUlMWV9JRCksIG4gPSBuKCkpDQpgYGANCg0KIyBTdGF0aXN0aWNhbCBhbmFseXNpcyBvZiBmZWF0dXJlcw0KDQpSZWFkIHN0YXRpc3RpY2FsIHJlc3VsdHMgZnJvbSBmaWxlOg0KYGBge3J9DQpza2luX3BfY2F0X2RpciA8LSByZWFkX3RzdigiVW50YXJnZXRlZC5wX2NhdF9kaXIudHN2IikNCnNraW5fcF92YWx1ZSAgIDwtIHJlYWRfdHN2KCJVbnRhcmdldGVkLnBfdmFsdWUudHN2IikNCmBgYA0KDQpgYGB7cn0NCnNraW5fcF9jYXRfZGlyICU+JSBjb2xuYW1lcygpDQpgYGANCg0KDQpgYGB7cn0NCnNraW5fc3RhdHMgPC0gc2tpbl9wX3ZhbHVlICU+JSANCiAgc2VsZWN0KE1FVF9DSEVNX05PKSAlPiUgDQogIGxlZnRfam9pbihza2luX3BfY2F0X2RpciwgYnkgPSAiTUVUX0NIRU1fTk8iKSAlPiUgDQogIG11dGF0ZSgNCiAgICBzZWJ1bWV0ZXJfMC4xX2FueSAgPSBgcF9jYXRfZGlyfGJhc2V8c2VidW1gICU+JSBpcy5uYSguKSAlPiUgbm90KCksDQogICAgc2VidW1ldGVyXzAuMV91cCAgID0gYHBfY2F0X2RpcnxiYXNlfHNlYnVtYCAlPiUgaXMubmEoLikgJT4lIG5vdCgpICYgYHBfY2F0X2RpcnxiYXNlfHNlYnVtYCAlPiUgc3RyX2RldGVjdCgiVXAiKSwNCiAgICBzZWJ1bWV0ZXJfMC4xX2Rvd24gPSBgcF9jYXRfZGlyfGJhc2V8c2VidW1gICU+JSBpcy5uYSguKSAlPiUgbm90KCkgJiBgcF9jYXRfZGlyfGJhc2V8c2VidW1gICU+JSBzdHJfZGV0ZWN0KCJEbiIpDQogICkNCg0Kc2tpbl9zdGF0cyAlPiUgDQogIGdyb3VwX2J5KGBwX2NhdF9kaXJ8YmFzZXxzZWJ1bWAsIHNlYnVtZXRlcl8wLjFfYW55KSAlPiUgc3VtbWFyaXplKC5ncm91cHMgPSAiZHJvcCIpDQoNCnNraW5fc3RhdHMgJT4lIA0KICBncm91cF9ieShgcF9jYXRfZGlyfGJhc2V8c2VidW1gLCBzZWJ1bWV0ZXJfMC4xX3VwKSAlPiUgc3VtbWFyaXplKC5ncm91cHMgPSAiZHJvcCIpDQoNCnNraW5fc3RhdHMgJT4lIA0KICBncm91cF9ieShgcF9jYXRfZGlyfGJhc2V8c2VidW1gLCBzZWJ1bWV0ZXJfMC4xX2Rvd24pICU+JSBzdW1tYXJpemUoLmdyb3VwcyA9ICJkcm9wIikNCmBgYA0KDQpDaGVjayB3aGV0aGVyIHRoZXJlIGFyZSBza2luIHN0YXRzIGZvciBhbGwgZmVhdHVyZXMgZnJvbSB0aGUgTUFTU1QgcmVzdWx0czoNCmBgYHtyfQ0KbWFzc3RfcmVzdWx0c19zdGF0cyA8LSBtYXNzdF9yZXN1bHRzICU+JSANCiAgaW5uZXJfam9pbigNCiAgICBza2luX3N0YXRzICU+JSANCiAgICAgIHNlbGVjdChGRUFUVVJFX0lEID0gTUVUX0NIRU1fTk8sIHNlYnVtZXRlcl8wLjFfYW55LCBzZWJ1bWV0ZXJfMC4xX3VwLCBzZWJ1bWV0ZXJfMC4xX2Rvd24pLA0KICAgIGJ5ID0gIkZFQVRVUkVfSUQiDQogICkNCg0Kc2V0ZGlmZihtYXNzdF9yZXN1bHRzJEZFQVRVUkVfSUQsIHNraW5fc3RhdHMkTUVUX0NIRU1fTk8pDQpgYGANCg0KKiBZZXMNCg0KIyBCYWN0ZXJpYSBpbiBGaWcuIDQgKE1NdmVjKQ0KIyMgU3RhcGh5bG9jb2NjdXMgZXBpZGVybWlkaXMNCg0KQXJlIHRoZXJlIGFueSBtYXNzdCBoaXRzIGZvciBTdGFwaHlsb2NvY2N1cyBlcGlkZXJtaWRpcz8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihOQU1FICU+JSBzdHJfdG9fbG93ZXIoKSAlPiUgc3RyX2RldGVjdCgic3RhcGgiKSkgJT4lIA0KICBzZWxlY3QoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lELCBwLnZhbHVlLmZkcikgJT4lIA0KICBhcnJhbmdlKE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCkNCmBgYA0KDQpXaGljaCBvZiB0aGVzZSBhcmUgY29ycmVsYXRlZCB3aXRoIHNlYnVtZXRlciBzY29yZT8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0c19zdGF0cyAlPiUgDQogIGZpbHRlcihOQU1FICU+JSBzdHJfdG9fbG93ZXIoKSAlPiUgc3RyX2RldGVjdCgic3RhcGgiKSAmIHNlYnVtZXRlcl8wLjFfYW55KSAlPiUgDQogIHNlbGVjdChOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIHAudmFsdWUuZmRyKSAlPiUgDQogIGFycmFuZ2UoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lEKQ0KYGBgDQoNCldoaWNoIG9mIHRoZXNlIGFyZSBpbiBvbmUgb2YgdGhlIGZhbWlsaWVzIGNvcnJlbGF0ZWQgd2l0aCBzZWJ1bWV0ZXIgc2NvcmU/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHNfc3RhdHMgJT4lIA0KICBmaWx0ZXIoDQogICAgTkFNRSAlPiUgc3RyX3RvX2xvd2VyKCkgJT4lIHN0cl9kZXRlY3QoInN0YXBoIikgJg0KICAgIEZBTUlMWV9JRCAlaW4lIGMoIlg5NDAwMjkiLCAiWDk1MDE5MCIsICJYOTQwMDA1IiwgIlg5NTAxNjciLCAiWDk1MDQ3NyIsICJYOTcwMDM0IikNCiAgICApICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyMgUHJvcGlvbmliYWN0ZXJpdW0gYWNuZXMNCg0KQXJlIHRoZXJlIGFueSBtYXNzdCBoaXRzIGZvciBQcm9waW9uaWJhY3Rlcml1bSBhY25lcz8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihOQU1FICU+JSBzdHJfdG9fbG93ZXIoKSAlPiUgc3RyX2RldGVjdCgicHJvcGlvbmliYWMiKSkgJT4lIA0KICBzZWxlY3QoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lELCBwLnZhbHVlLmZkcikgJT4lIA0KICBhcnJhbmdlKE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCkNCmBgYA0KDQpXaGljaCBvZiB0aGVzZSBhcmUgY29ycmVsYXRlZCB3aXRoIHNlYnVtZXRlciBzY29yZT8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0c19zdGF0cyAlPiUgDQogIGZpbHRlcihOQU1FICU+JSBzdHJfdG9fbG93ZXIoKSAlPiUgc3RyX2RldGVjdCgicHJvcGlvbmliYWMiKSAmIHNlYnVtZXRlcl8wLjFfYW55KSAlPiUgDQogIHNlbGVjdChOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIHAudmFsdWUuZmRyKSAlPiUgDQogIGFycmFuZ2UoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lEKQ0KYGBgDQoNCldoaWNoIG9mIHRoZXNlIGFyZSBpbiBvbmUgb2YgdGhlIGZhbWlsaWVzIGNvcnJlbGF0ZWQgd2l0aCBzZWJ1bWV0ZXIgc2NvcmU/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHNfc3RhdHMgJT4lIA0KICBmaWx0ZXIoDQogICAgTkFNRSAlPiUgc3RyX3RvX2xvd2VyKCkgJT4lIHN0cl9kZXRlY3QoInByb3Bpb25pYmFjIikgJg0KICAgIEZBTUlMWV9JRCAlaW4lIGMoIlg5NDAwMjkiLCAiWDk1MDE5MCIsICJYOTQwMDA1IiwgIlg5NTAxNjciLCAiWDk1MDQ3NyIsICJYOTcwMDM0IikNCiAgICApICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyBTcGVjaWVzIGluIHRoZSBmYW1pbGllcyBjb3JyZWxhdGVkIHdpdGggc2VidW1ldGVyIHNjb3JlDQojIyBGYW1pbHkgWDk0MDAyOQ0KDQohW1g5NDAwMjldKDk0MDAyOSBGYXR0eSBhY2lkIG1ldGh5bCBvciBldGh5bCBlc3RlcnMucG5nKQ0KDQpXaGljaCBzcGVjaWVzIGFyZSBpbiB0aGUgZmFtaWx5IFg5NDAwMjk/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTQwMDI5IikgJT4lIA0KICBzZWxlY3QoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lELCBwLnZhbHVlLmZkcikgJT4lIA0KICBhcnJhbmdlKE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCkNCg0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihGQU1JTFlfSUQgPT0gIlg5NDAwMjkiKSAlPiUgDQogIGNvdW50KE5BTUUpDQpgYGANCg0KV2hpY2ggZmVhdHVyZXMgd2l0aCBNQVNTVCBoaXRzIGFyZSBpbiB0aGUgZmFtaWx5IFg5NDAwMjk/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTQwMDI5IikgJT4lIA0KICBzZWxlY3QoRkFNSUxZX0lELCBGRUFUVVJFX0lELCBOQU1FLCBwLnZhbHVlLmZkcikgJT4lIA0KICBhcnJhbmdlKEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgTkFNRSkNCg0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihGQU1JTFlfSUQgPT0gIlg5NDAwMjkiKSAlPiUgDQogIGNvdW50KEZBTUlMWV9JRCwgRkVBVFVSRV9JRCkNCmBgYA0KDQojIyBGYW1pbHkgWDk1MDE5MA0KDQohW1g5NTAxOTBdKFg5NTAxOTAucG5nKQ0KDQpXaGljaCBzcGVjaWVzIGFyZSBpbiB0aGUgZmFtaWx5IFg5NTAxOTA/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTUwMTkwIikgJT4lIA0KICBzZWxlY3QoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lELCBwLnZhbHVlLmZkcikgJT4lIA0KICBhcnJhbmdlKE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCkNCmBgYA0KDQpXaGljaCBmZWF0dXJlcyB3aXRoIE1BU1NUIGhpdHMgYXJlIGluIHRoZSBmYW1pbHkgWDk1MDE5MD8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihGQU1JTFlfSUQgPT0gIlg5NTAxOTAiKSAlPiUgDQogIHNlbGVjdChGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIE5BTUUsIHAudmFsdWUuZmRyKSAlPiUgDQogIGFycmFuZ2UoRkFNSUxZX0lELCBGRUFUVVJFX0lELCBOQU1FKQ0KYGBgDQoNCiMjIEZhbWlseSBYOTQwMDA1DQoNCiFbWDk0MDAwNV0oOTQwMDA1LnBuZykNCg0KV2hpY2ggc3BlY2llcyBhcmUgaW4gdGhlIGZhbWlseSBYOTQwMDA1Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk0MDAwNSIpICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTQwMDA1IikgJT4lIA0KICBjb3VudChOQU1FKQ0KYGBgDQoNCldoaWNoIGZlYXR1cmVzIHdpdGggTUFTU1QgaGl0cyBhcmUgaW4gdGhlIGZhbWlseSBYOTQwMDA1Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk0MDAwNSIpICU+JSANCiAgc2VsZWN0KEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgTkFNRSwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIE5BTUUpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTQwMDA1IikgJT4lIA0KICBjb3VudChGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyMgRmFtaWx5IFg5NTAxNjcNCg0KIVtYOTUwMTY3XShYOTUwMTY3LnBuZykNCg0KV2hpY2ggc3BlY2llcyBhcmUgaW4gdGhlIGZhbWlseSBYOTUwMTY3Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk1MDE2NyIpICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTUwMTY3IikgJT4lIA0KICBjb3VudChOQU1FKQ0KYGBgDQoNCldoaWNoIGZlYXR1cmVzIHdpdGggTUFTU1QgaGl0cyBhcmUgaW4gdGhlIGZhbWlseSBYOTUwMTY3Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk1MDE2NyIpICU+JSANCiAgc2VsZWN0KEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgTkFNRSwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIE5BTUUpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTUwMTY3IikgJT4lIA0KICBjb3VudChGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyMgRmFtaWx5IFg5NTA0NzcNCg0KIVtYOTUwNDc3XShYOTUwNDc3LnBuZykNCg0KV2hpY2ggc3BlY2llcyBhcmUgaW4gdGhlIGZhbWlseSBYOTUwNDc3Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk1MDQ3NyIpICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTUwNDc3IikgJT4lIA0KICBjb3VudChOQU1FKQ0KYGBgDQoNCldoaWNoIGZlYXR1cmVzIHdpdGggTUFTU1QgaGl0cyBhcmUgaW4gdGhlIGZhbWlseSBYOTUwNDc3Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk1MDQ3NyIpICU+JSANCiAgc2VsZWN0KEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgTkFNRSwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIE5BTUUpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTUwNDc3IikgJT4lIA0KICBjb3VudChGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyMgRmFtaWx5IFg5NzAwMzQNCg0KIVtYOTcwMDM0XShYOTcwMDM0LnBuZykNCg0KV2hpY2ggc3BlY2llcyBhcmUgaW4gdGhlIGZhbWlseSBYOTcwMDM0Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk3MDAzNCIpICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTcwMDM0IikgJT4lIA0KICBjb3VudChOQU1FKQ0KYGBgDQoNCldoaWNoIGZlYXR1cmVzIHdpdGggTUFTU1QgaGl0cyBhcmUgaW4gdGhlIGZhbWlseSBYOTcwMDM0Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk3MDAzNCIpICU+JSANCiAgc2VsZWN0KEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgTkFNRSwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIE5BTUUpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTcwMDM0IikgJT4lIA0KICBjb3VudChGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyBTZXNzaW9uIGluZm8NCg0KYGBge3J9DQpzZXNzaW9uSW5mbygpDQpgYGANCg==